package com.yunji.coupon.business;

import cn.hutool.core.lang.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.yunji.cabinet.domain.ContainerDO;
import com.yunji.common.exception.ServiceException;
import com.yunji.common.utils.ConvertDataUtil;
import com.yunji.common.utils.JsonUtil;
import com.yunji.coupon.bo.*;
import com.yunji.coupon.consumer.CouponConsumer;
import com.yunji.coupon.doamin.CouponDO;
import com.yunji.coupon.doamin.CouponOrderDO;
import com.yunji.coupon.doamin.CouponSiteDO;
import com.yunji.coupon.doamin.CouponTmplDO;
import com.yunji.coupon.dto.*;
import com.yunji.coupon.engine.CouponConvert;
import com.yunji.coupon.engine.CouponRuleEngine;
import com.yunji.coupon.engine.CouponValidate;
import com.yunji.coupon.enums.CouponRecordEnum;
import com.yunji.coupon.enums.CouponStatusEnum;
import com.yunji.coupon.enums.TriggerTypeEnum;
import com.yunji.coupon.service.CouponService;
import com.yunji.coupon.vo.CouponTmplListVO;
import com.yunji.coupon.vo.CouponVO;
import com.yunji.coupon.vo.UseCouponVO;
import com.yunji.order.domain.OrderDO;
import com.yunji.system.context.SystemContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : peter-zhu
 * @date : 2024/10/21 10:37
 * @description : 优惠券业务
 **/
@Slf4j
@Service
public class CouponBusiness {

    @Resource
    private CouponService couponService;

    @Resource
    private CouponRuleEngine couponRuleEngine;

    @Resource
    private CouponConsumer couponConsumer;

    public Set<String> likeNames(String name) {
        return couponService.likeNames(name);
    }

    public List<CouponTmplListVO> tmplPageList(CouponTmplPageDTO dto) {
        return couponService.tmplPageList(dto);
    }

    public boolean createTmpl(CouponTmplCreateDTO dto) {
        Pair<CouponTmplDO, List<CouponSiteDO>> rs = createOrModify(dto);
        boolean couponTmpl = couponService.createCouponTmpl(rs.getKey(), rs.getValue());
        dto.setCouponTmplId(rs.getKey().getCouponTmplId());
        return couponTmpl;
    }

    public boolean modifyTmpl(CouponTmplCreateDTO dto) {
        Pair<CouponTmplDO, List<CouponSiteDO>> rs = createOrModify(dto);
        return couponService.modifyCouponTmpl(rs.getKey(), rs.getValue());
    }

    private Pair<CouponTmplDO, List<CouponSiteDO>> createOrModify(CouponTmplCreateDTO dto) {
        CouponValidate.validateCouponTmpl(dto);
        CouponTmplDO domain = CouponConvert.tmpl2DO(dto);
        List<CouponSiteDO> couponSiteDOS = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(dto.getCouponSiteList()))
            //if (CouponSiteScopeEnum.SCOPE.getCode().equals(dto.getSiteScopeType())) {
            couponSiteDOS.addAll(CouponConvert.sites2DOList(dto.getCouponSiteList(), couponConsumer, dto.getSiteScopeType()));
        //}
//        else if (CouponSiteScopeEnum.EXCLUDE_SCOPE.getCode().equals(dto.getSiteScopeType())) {
//            couponSiteDOS.addAll(CouponConvert.sites2DOList(dto.getExcludeSiteList(), couponConsumer, CouponSiteScopeEnum.EXCLUDE_SCOPE.getCode()));
//        }
        return new Pair<>(domain, couponSiteDOS);
    }

    public boolean removeCouponTmpl(Long id) {
        boolean receive = couponService.isReceive(id);
        if (receive)
            throw new ServiceException("已经领取过的优惠券模板不能删除");
        return couponService.removeCouponTmpl(id);
    }

    public CouponTmplCreateDTO getCouponTmpl(Long id) {
        return couponService.getCouponTmpl(id);
    }

    public boolean modifyReceiveTime(Long id, Integer action) {
        return couponService.modifyReceiveTime(id, action);
    }

    public boolean modifyRecall(Long id) {
        return couponService.modifyRecall(id);
    }

    public List<CouponVO> getUnReceiveList(Long orderId) {
        OrderDO order = couponConsumer.getByOrderId(orderId);
        Long siteId = order.getSiteId();
        //不需要指定系统弹窗 所以构造函数无需传入triggerDic
        List<CouponTmplDO> runningTmplList = couponService.runningTmplList(new CouponRunningTmplBO(null, TriggerTypeEnum.RECEIVE.getCode()));
        List<CouponVO> availableList = Lists.newArrayList();
        for (CouponTmplDO domain : runningTmplList) {
            Pair<Boolean, String> rs = couponRuleEngine.ruleSitesFilter(domain, siteId);
            if (rs.getKey()) {
                CouponBO couponBO = couponRuleEngine.getAvailableCoupon(domain);
                if (couponBO != null && couponBO.isAvailable()) {
                    availableList.add(CouponConvert.convert2CouponVO(couponBO));
                }
            }
        }
        return availableList;
    }

    public List<CouponBO> unUsedSelectBOList(Long siteId) {
        CouponListDTO listDTO = new CouponListDTO();
        listDTO.setStatusList(Sets.newHashSet(CouponStatusEnum.UN_USED.getCode()));
        listDTO.setUserId(SystemContext.getUserId());
        List<CouponDO> unUsedList = couponService.couponDOList(listDTO);
        List<Long> tmplIds = ConvertDataUtil.list2FieldList(unUsedList, CouponDO::getCouponTmplId);
        if (CollectionUtils.isEmpty(tmplIds)) {
            return Lists.newArrayList();
        }
        Map<Long, CouponTmplDO> tmplMap = ConvertDataUtil.getDataMap(() -> couponService.getTmplListByIds(tmplIds), CouponTmplDO::getCouponTmplId);
        List<CouponBO> list = Lists.newArrayList();
        for (CouponDO domain : unUsedList) {
            CouponTmplDO couponTmplDO = tmplMap.get(domain.getCouponTmplId());
            if (couponTmplDO != null) {
                CouponBO couponBO = new CouponBO();
                couponBO.setTmplDO(couponTmplDO);
                couponBO.setDomain(domain);
                couponBO.setSortTime(domain.getValidPeriodE());
                Pair<Boolean, String> rs = new Pair<>(true, null);
                if (rs.getKey()) {
                    rs = couponRuleEngine.ruleUseValid(domain);
                }
                if (rs.getKey()) {
                    // 过滤网点数据
                    rs = couponRuleEngine.ruleSitesFilter(couponTmplDO, siteId);
                }
                couponBO.setAvailable(rs.getKey());
                couponBO.setReason(rs.getValue());
                list.add(couponBO);
            }
        }
        list = list.stream().sorted(Comparator.comparing(CouponBO::getSortTime)).collect(Collectors.toList());
        return list;
    }

    public UseCouponVO unUsedSelectList(Long siteId) {
        List<CouponBO> couponBOS = unUsedSelectBOList(siteId);
        List<CouponVO> availableList = Lists.newArrayList();
        List<CouponVO> unAvailableList = Lists.newArrayList();
        for (CouponBO couponBO : couponBOS) {
            if (couponBO.isAvailable()) {
                availableList.add(CouponConvert.convert2CouponVO(couponBO));
            } else {
                unAvailableList.add(CouponConvert.convert2CouponVO(couponBO));
            }
        }
        return new UseCouponVO(availableList, unAvailableList);
    }

    // 系统自动领取不需要事务，领取失败是因为规则变动 或者被抢完
    public List<CouponVO> systemAutoReceive(SystemReceiveDTO dto) {
        List<CouponTmplDO> runningTmplList = couponService.runningTmplList(new CouponRunningTmplBO(dto.getTriggerDic(), TriggerTypeEnum.SYSTEM.getCode()));
        List<CouponVO> availableList = Lists.newArrayList();
        ContainerDO cabinet = couponConsumer.getCabinetByQrcode(dto.getQrCode());
        for (CouponTmplDO domain : runningTmplList) {
            CouponBO couponBO = couponRuleEngine.getAvailableCoupon(domain);
            Pair<Boolean, String> rs = couponRuleEngine.ruleSitesFilter(domain, cabinet.getSiteId());
            if (couponBO != null && rs.getKey()) {
                if (couponBO.isAvailable()) {
                    boolean receiveSuccess = couponService.receiveCoupon(couponBO);
                    if (!receiveSuccess) {
                        log.error("优惠券领取失败:{}", JsonUtil.toJsonString(couponBO));
                    } else {
                        availableList.add(CouponConvert.convert2CouponVO(couponBO));
                    }
                } else {
                    log.debug("领劵数据{},失败原因:{}", JsonUtil.toJsonString(domain), couponBO.getReason());
                }
            }
        }
        return availableList;
    }

    public CouponBO receiveAvailableCoupon(Long couponTmplId) {
        CouponTmplDO couponTmplDO = couponService.getCouponTmplDomain(couponTmplId);
        CouponBO couponBO = couponRuleEngine.getAvailableCoupon(couponTmplDO);
        Optional.ofNullable(couponBO).orElseThrow(() -> {
            String errorMsg = "优惠券领取失败,优惠券模板为空,模板id:" + couponTmplId + "微信用户id" + SystemContext.getUserId();
            log.error("receiveAvailableCoupon fail tmpl is null {}", errorMsg);
            return new RuntimeException(errorMsg);
        });
        boolean receiveSuccess = couponService.receiveCoupon(couponBO);
        if (!receiveSuccess) {
            log.error("receiveAvailableCoupon fail:{}", JsonUtil.toJsonString(couponBO));
        }
        return couponBO;
    }

    public List<CouponVO> userCouponList(CouponListDTO dto) {
        //FIXME 上线前改为下面注释代码
        dto.setUserId(SystemContext.getUserId());
        return couponService.couponList(dto);
    }

    public List<CouponVO> couponList(CouponListDTO dto) {
        return couponService.couponList(dto);
    }

    public List<CouponVO> couponList(CouponPageDTO dto) {
        return couponService.couponList(dto);
    }

    public List<CouponVO> couponPageList(CouponPageDTO dto) {
        return couponService.couponPageList(dto);
    }

    // 根据订单销售额获取优惠金额
    public CouponAmtBO getCouponAmtById(Long couponId, BigDecimal orderSaleAmt) {
        CouponBO byId = couponService.getCouponBO(couponId);
        if (byId == null) return null;
        BigDecimal rs = CouponRuleEngine.getCouponAmt(byId.getTmplDO().getDiscountType(), orderSaleAmt, byId.getTmplDO().getDiscountValue());
        return new CouponAmtBO(couponId, rs);
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean useCoupon(UseCouponBO bo) {
        CouponOrderDO domain = bo.getDomain();
        Long couponId = domain.getCouponId();
        boolean existsByCid = couponService.existsByCid(couponId);
        //如果优惠券存在 说明被订单使用过
        if (existsByCid) return false;
        boolean rs = couponService.saveOrderCoupon(domain);
        CouponDO couponDO = couponService.getById(couponId);
        ServiceException.throwException(couponDO == null, "没有找到优惠券");
        boolean b = couponService.modifyUsedCount(couponDO.getCouponTmplId(), 1);
        couponService.addRecord(CouponConvert.convert2Record(couponDO, domain.getOrderId(), domain.getOrderCode(), bo.getAmt(), CouponRecordEnum.USED.name()));
        return rs && b && couponService.modifyCouponStatus(new ModifyCouponStatusBO(couponId, CouponStatusEnum.UN_USED.getCode(), CouponStatusEnum.USED.getCode()));
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean backCoupon(Long orderId) {
        Long couponId = couponService.getCidByOid(orderId);
        //等于NULL 说明被订单没有使用过该优惠券
        if (couponId == null) return false;
        boolean rs = couponService.removeOrderCoupon(orderId);
        Integer newStatus = null;
        if (rs) {
            CouponBO couponBO = couponService.getCouponBO(couponId);
            CouponDO domain = couponBO.getDomain();
            boolean valid = CouponRuleEngine.isValid(domain.getValidPeriodS(), domain.getValidPeriodE());
            if (valid) {
                CouponTmplDO tmplDO = couponBO.getTmplDO();
                newStatus = tmplDO.isRecallFlag() ? CouponStatusEnum.RECALL.getCode() : CouponStatusEnum.UN_USED.getCode();
            } else {
                // 未使用会根据时间判断自动变为无效
                newStatus = CouponStatusEnum.UN_USED.getCode();
            }
            rs = couponService.modifyUsedCount(domain.getCouponTmplId(), -1);
            couponService.addRecord(CouponConvert.convert2Record(domain, orderId, null, null, CouponRecordEnum.BACK.name()));
        }
        return rs && couponService.modifyCouponStatus(new ModifyCouponStatusBO(couponId, CouponStatusEnum.USED.getCode(), newStatus));
    }

    public List<CouponTmplListVO> tmplList(CouponTmplPageDTO dto) {
        return couponService.tmplList(dto);
    }

    public CouponBO getCouponBO(Long couponId) {
        return couponService.getCouponBO(couponId);
    }
}
